import { CLIENT, TRANSACTION, RCPROTO } from "rclink";
import fs from "fs";
import {
    CHAINCODE_DEPLOY,
    CHAINCODE_INVOKE,
    CHAINCODE_SET_STATE,
    REQUEST_FAILURE,
    REQUEST_SUCCESS,
    TX_STATUS_CONFIRMED,
    TX_STATUS_UNCONFIRMED,
} from "./const";
import logger from "./log";
import myConfig from "./config";

const {
    enableClientSpecifyChaincode = true,
    enableClientSpecifyTxType = true,
    defaultTxType = CHAINCODE_INVOKE,
    defaultChaincodeName,
    defaultChaincodeVersion,
    defaultChaincodeFunction,
    url,
} = myConfig.RepChain || {};
const {
    prvKeyPath, pubKeyPath, accountId, accountCertName,
} = myConfig.identity || {};

const prvKey = fs.readFileSync(prvKeyPath).toString();
const pubKey = fs.readFileSync(pubKeyPath).toString();

export const generateTx = (req, res) => {
    let chaincodeName;
    let chaincodeVersion;
    let chaincodeFunction;
    const { contractName, contractVersion, contractFunction } = req.body;
    if (enableClientSpecifyChaincode) {
        chaincodeName = contractName || defaultChaincodeName;
        chaincodeVersion = contractVersion || defaultChaincodeVersion;
        chaincodeFunction = contractFunction || defaultChaincodeFunction;
    } else {
        chaincodeName = defaultChaincodeName;
        chaincodeVersion = defaultChaincodeVersion;
        chaincodeFunction = defaultChaincodeFunction;
    }
    let type;
    let { txid } = req.body;
    const {
        gasLimit, oid, args, txType,
    } = req.body;
    if (enableClientSpecifyTxType) {
        type = txType || defaultTxType;
    } else {
        type = defaultTxType;
    }
    if (!chaincodeName) {
        logger.error("Not config defaultChaincodeName");
        res.status(500)
            .json({ code: REQUEST_FAILURE, message: "not config defaultChaincodeName" });
    }
    if (!chaincodeVersion) {
        logger.error("Not config defaultChaincodeVersion");
        res.status(500)
            .json({ code: REQUEST_FAILURE, message: "not config defaultChaincodeVersion" });
    }
    if (!chaincodeFunction && type === CHAINCODE_INVOKE) {
        logger.error("Not config defaultChaincodeFunction");
        res.status(500)
            .json({ code: REQUEST_FAILURE, message: "not config defaultChaincodeFunction" });
    }

    const txConsArgs = {
        txid,
        chaincodeName,
        chaincodeVersion,
        gasLimit,
        oid,
    };
    if (type === CHAINCODE_DEPLOY) {
        txConsArgs.type = RCPROTO.rep.proto.Transaction.Type.CHAINCODE_DEPLOY;
        txConsArgs.chaincodeDeployParams = args;
    }
    if (type === CHAINCODE_INVOKE) {
        txConsArgs.type = RCPROTO.rep.proto.Transaction.Type.CHAINCODE_INVOKE;
        txConsArgs.chaincodeInvokeParams = {
            chaincodeFunction,
            chaincodeFunctionArgs: [args],
        };
    }
    if (type === CHAINCODE_SET_STATE) {
        txConsArgs.type = RCPROTO.rep.proto.Transaction.Type.CHAINCODE_SET_STATE;
        txConsArgs.chaincodeSetStateParams = { state: args };
    }
    const transaction = TRANSACTION.Transaction.create(txConsArgs);
    transaction.sign({
        prvKey,
        pubKey,
        alg: "sha256",
        creditCode: accountId,
        certName: accountCertName,
    });
    txid = transaction.getTxSignedTxid();
    const txSignedBytes = transaction.getTxSignedBuffer();
    logger.info(`Generated the signed transaction(txid: ${txid}, type: ${type}, chaincodeName: ${txConsArgs.chaincodeName}, chaincodeVersion: ${txConsArgs.chaincodeVersion}) for client`);
    const repchainClient = new CLIENT.Client(url);
    logger.info(`Sending the transaction(txid: ${txid}) to the blockchain node(${url})`);
    repchainClient.sendTransaction(txSignedBytes.toString("hex")).then((result) => {
        logger.info(`Received the transaction(txid: ${txid}) post result from the blockchain node(${url})`);
        if (result.err) {
            logger.error(`Got the error message(${result.err}) for the transaction(${txid}) from the blockchain node(${url})`);
            res.status(500)
                .json({ txid, code: REQUEST_FAILURE, message: `Got the error message from RepChain: ${result.err}` });
        } else {
            logger.info(`Post the transaction(${txid}) successfully to the blockchain node(${url})`);
            res.status(200)
                .json({ txid, code: REQUEST_SUCCESS, message: "Post the transaction successfully" });
        }
    }).catch((err) => {
        logger.error(`Failed to post the transaction(${txid}), with the error: `, err);
        res.status(500)
            .json({ txid, code: REQUEST_FAILURE, message: `Failed to post the transaction, with the error: ${err.message}` });
    });
};

export const getTx = (req, res) => {
    const repchainClient = new CLIENT.Client(url);
    const { id: txid } = req.params;
    logger.info(`Querying the transaction(${txid}) status from the blockchain node${url} for client`);
    repchainClient.transaction(txid, "JSON", true)
        .then(async (result) => {
            if (result && result.tranInfo && result.tranInfo.id === txid) {
                const blockHeight = result.height;
                const { blockHash, status, errMsg } = await repchainClient.block(blockHeight)
                    .then((block) => {
                        const transactionResult = block.transactionResults
                            .find((e) => e.txId === txid).err;
                        let txStatus;
                        if (Object.keys(transactionResult).length === 0) {
                            txStatus = true;
                        } else {
                            txStatus = transactionResult.code === 0;
                        }
                        let txErrMsg;
                        if (!txStatus) {
                            txErrMsg = transactionResult.reason;
                        }
                        return {
                            blockHash: block.header.hashPresent,
                            status: txStatus,
                            errMsg: txErrMsg,
                        };
                    });
                if (status) {
                    logger.info(`Got the transaction(${txid} status: ${TX_STATUS_CONFIRMED} from the blockchain node(${url})`);
                    res.status(200)
                        .json({
                            txid,
                            blockHash,
                            code: REQUEST_SUCCESS,
                            status: TX_STATUS_CONFIRMED,
                            message: "The transaction has been confirmed in the blockchain",
                        });
                } else {
                    logger.info(`Got the transaction(${txid} status: ${TX_STATUS_UNCONFIRMED} from the blockchain node(${url})`);
                    res.status(200)
                        .json({
                            txid,
                            code: REQUEST_SUCCESS,
                            status: TX_STATUS_UNCONFIRMED,
                            message: `The transaction is not confirmed in the blockchain for the reason: ${errMsg}`,
                        });
                }
            } else {
                logger.info(`Got the transaction(${txid} status: ${TX_STATUS_UNCONFIRMED} from the blockchain node(${url})`);
                res.status(200)
                    .json({
                        txid,
                        code: REQUEST_SUCCESS,
                        status: TX_STATUS_UNCONFIRMED,
                        message: "The transaction is not confirmed in the blockchain for some reason unknown",
                    });
            }
        }).catch((err) => {
            logger.error(`Failed to query the transaction status, from the blockchain node(${url}) with the error: `, err);
            res.status(500)
                .json({ txid, code: REQUEST_FAILURE, message: `Failed to query the transaction status, with the error: ${err.message}` });
        });
};
